-module(life).
-compile(export_all).

% Two dimensional grid

% grid_get(Row, Col, Grid)
% Returns Grid[Row mod W, Col mod H], where W and H are the
% width and height of Grid
% PRE: -1 <= Row <= W, -1 <= Col <= H
grid_get(Row, Col, Grid) ->
    RowSize = array:size(Grid),
    if
	Row	== -1 ->
	    R = RowSize - 1;
	Row == RowSize ->
	    R = 0;
	true ->
	    R = Row
    end,
    ColSize = array:size(array:get(R, Grid)),
    if
	Col == -1 ->
	    C = ColSize - 1;
	Col == ColSize ->
	    C = 0;
	true ->
	    C = Col
    end,
    array:get(C, array:get(R, Grid)).

% grid_set(Row, Col, Val, Grid)
% Sets Grid[Row, Col] to Val and returns the new grid,
% PRE: 0 <= Row < W, 0 <= Col < H, where W and H are the
% width and height of Grid
grid_set(Row, Col, Val, Grid) ->
    array:set(Row, array:set(Col, Val, array:get(Row, Grid)),
        Grid).

% grid_from_list(L)
% Creates a grid from the two dimensional list L
% PRE: Each sublist of L is of the same length
grid_from_list(L) ->
    array:from_list(lists:map(fun array:from_list/1, L)).

% grid_to_list(G)
% Creates a two-dimensional list from the grid G
grid_to_list(G) ->
    array:sparse_to_list(array:map(fun (_,Y) ->
                    array:sparse_to_list(Y) end,
            G)).

% Initialization

% % Creating cells

% create_cells(L)
% Spawns processes running new_cell, from the two-dimensional
% list L, initializing their State to the values of L, giving them 
% row-coordinates starting from 0, and returns a two-
% dimensional list of these PIDs
create_cells(L) ->
    life:create_cells(L, 0).

% create_cells(L, Row)
% Spawns processes running new_cell, from the two-dimensional
% list L, initializing their State to the values of L, giving them 
% row-coordinates starting from 0, and returns a two-
% dimensional list of these PIDs
create_cells([], _) -> [];
create_cells([Row | Rest], R) ->
    [life:create_row(Row, {R, 0}) |
     life:create_cells(Rest, R+1)].

% create_row(L, {R, C})
% Spawns processes running new_cell, from the list L, initializing
% their State to the values of L, giving them row-coordinates R and
% column-coordinates starting from C, and returns a list of these PIDs
create_row([], _) -> [];
create_row([F | Rest], {R, C}) ->
    [spawn(life, new_cell, [F, {R, C}, self()]) |
     life:create_row(Rest, {R, C+1})].

% % Distributing neighbours

% get_neighbours(Row, Col, Grid)
% Returns a list of the eight neighbours of Grid[Row, Col],
% boundary elements gets neighbours by wrapping
% PRE: 0 <= Row < W, 0 <= Col < H, where W and H are the
% width and height of Grid
get_neighbours(Row, Col, Grid) ->
    lists:map(fun ({R, C}) -> life:grid_get(R, C, Grid) end,
	      [{Row-1, Col-1}, {Row-1, Col}, {Row-1, Col+1},
	       {Row, Col-1},                 {Row, Col+1},
	      {Row+1, Col-1}, {Row+1, Col}, {Row+1, Col+1}]).

% distribute_neighbours(Grid)
% Sends the neighbours of each element of Grid to the
% corresponding PID
% PRE: Grid is a non-empty grid of PIDs
distribute_neighbours(Grid) ->
    Rows = array:size(Grid) - 1,
    Cols = array:size(array:get(0, Grid))-1,
    life:distribute_neighbours(Grid, Rows, Cols).

% distribute_neighbours(Grid, Row, Col)
% Sends the neighbours of each element of Grid with Y-coordinate <= Row,
% starting at Grid[Row, Col], to the corresponding PID
% PRE: Grid is a non-empty grid of PIDs, Col >= 0
distribute_neighbours(_, Row, _) when Row < 0 ->
    done;
distribute_neighbours(Grid, Row, Col) ->
    life:grid_get(Row, Col, Grid) ! {neighbours,
        life:get_neighbours(Row, Col, Grid)},
    if
	Col == 0 ->
	    life:distribute_neighbours(Grid, Row - 1,
				       array:size(array:get(Row, Grid))-1);
	true ->
	    life:distribute_neighbours(Grid, Row, Col - 1)
    end.

% % Start-up

% *Public*
% init(L)
% Initiates a game of life from the states of the two-dimensional
% list L, as well as a master process. Returns the PID of the master.
init(L) ->
    spawn(life, init_master, [L]).

% init_master(L)
% Spawns processes running new_cell, from the two-dimensional
% list L, initializing their State to the values of L, then distributes
% the neighbours of each cell. Returns the PID master.
init_master(L) ->
    Cells = life:create_cells(L),
    life:distribute_neighbours(grid_from_list(Cells)),
    life:master(hd(hd(Cells)),{length(L), length(hd(L))}).


% *Public*
% random_life(R, C)
% Creates a two dimensional list of size RxC, filled with
% random ones and zeroes.
% PRE: R >= 0, C>0
random_life(0, _) ->
    [];
random_life(R, C) ->
    [random_row(C) |
     random_life(R-1, C)].

% random_row(N)
% Creates a list of length N with random ones and zeroes
% PRE: N >= 0
random_row(0) ->
    [];
random_row(N) ->
    [random:uniform(2)-1 |
     random_row(N-1)].


% Managing the game of life

% % Cells

% calc_state(State, Life)
% Returns the new state of a cell where State is the current state and
% Life is the sum the states of the surrounding cells
calc_state(_, 3) -> 1;
calc_state(1, 2) -> 1;
calc_state(_,_) -> 0.

% new_cell(State, ID, Master)
% Waits for a list of neighbours before assuming normal cell activity.
% State is the initial state of the cell, ID is the coordinates of the cell
% and Master is the PID of the master.
new_cell(State, ID, Master) ->
    receive
        {neighbours, N} ->
            life:cell(State, ID, N, Master, 8, 0, passive)
    end.

% cell(State, ID, Neighbours, Master, Acc, Life, Activity)
% If Activity is passive, the cell waits for quit, tick (start broadcasting
% state), or the state of a neighbouring cell (which also starts
% broadcasting). If active, the cell only waits for the neighbouring
% states. On each received state Acc is reduced by one. When Acc is
% zero, all neighbouring states have been received and the new state
% is sent to the master along with the ID. Finally the cell enters
% passive mode again. 
% State is the current state, ID the coordinates in the grid,
% Neighbours is a list with the PIDs of the neighbouring cells
% Master is the PID of the master process and Life is the accumulated
% sum of the states of the neighbouring cells.
cell(State, ID, Neighbours, Master, 0, Life, _) ->
    NewState = life:calc_state(State, Life),
    Master ! {ID, NewState},
    life:cell(NewState, ID, Neighbours, Master, 8, 0, passive);
cell(State, ID, Neighbours, Master, Acc, Life, passive) ->
    receive
	quit ->
	    lists:foreach((fun(N) -> N ! quit end), Neighbours),
	    exit(done);
	tick ->
	    lists:foreach((fun(N) -> N ! {state, State} end),
			  Neighbours),
	    life:cell(State, ID, Neighbours, Master, Acc, Life,
		      active);
	{state, Status} ->
	    lists:foreach((fun(N) -> N ! {state, State} end),
			  Neighbours),
	    life:cell(State, ID, Neighbours, Master, Acc-1,
		      Life + Status, active);
	_ ->
	    cell(State, ID, Neighbours, Master, Acc, Life, passive)
    end;
cell(State, ID, Neighbours, Master, Acc, Life, active) ->
    receive
        {state, Status} ->
	    life:cell(State, ID, Neighbours, Master, Acc-1,
		      Life+Status, active)
    end.

% % The master coordinator

% master(Cell, {R, C})
% The master process of a game of life grid of size RxC. Waits for a
% tick or quit message and forwards it to Cell. After sending a tick,
% master calls collect and returns the new state of the board to the
% process that sent the tick in the first place.
% PRE: Cell is a cell in the game of life
master(Cell, {R, C}) ->
    receive
        {tick, PID} ->
            Cell ! tick,
            PID ! {result, life:grid_to_list(
			     life:collect(
			       array:new(R,{default,
					    array:new(C)}), R*C))},
            life:master(Cell, {R, C});
        quit ->
            Cell ! quit
    end.

% collect(Data, Count)
% Collects Count states from a game of life and inserts them into
% the grid Data, using the coordinates received with the state.
% PRE: Count >= 0
collect(Data, 0) -> Data;
collect(Data, Count) ->
    receive
        {{R, C}, NewState} ->
            life:collect(grid_set(R, C, NewState, Data),
                Count-1)
    end.

% % Controlling time

% tick(MID)
% Sends a tick to the process with PID MID and receives and returns
% the resulting data.
% Public
tick(MID) ->
    MID ! {tick, self()},
    receive
        {result, Data} ->
            Data
    end.

% % Printing

% *Public*
% print_life(L)
% Prints the two-dimensional list L.
print_life(L) ->
    lists:foreach(
      fun (R) ->
	      lists:foreach(
		fun (C) ->
			io:format("~w", [C])
		end, R),
	      io:format("~n")
      end,
      L).
